package jason.eis;

import static jason.eis.Translator.literalToAction;
import static jason.eis.Translator.perceptToLiteral;
import jason.JasonException;
import jason.asSyntax.ASSyntax;
import jason.asSyntax.Literal;
import jason.asSyntax.StringTerm;
import jason.asSyntax.Structure;
import jason.asSyntax.Term;
import jason.environment.Environment;

import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import cartago.CartagoException;
import cartago.CartagoService;
import cartago.ICartagoSession;

import c4jason.CAgentArch;
import c4jason.CartagoEnvironment;

import eis.AgentListener;
import eis.EILoader;
import eis.EnvironmentInterfaceStandard;
import eis.EnvironmentListener;
import eis.iilang.EnvironmentState;
import eis.iilang.Parameter;
import eis.iilang.Percept;

/**
 * This class adapts an EIS environment to be used as a Jason environment (see
 * http://cig.in.tu-clausthal.de/eis)
 * 
 * @author Jomi
 */
public class EISAdapter extends Environment implements AgentListener {

	private static EISAdapter instance;

	private Logger logger = Logger.getLogger("EISAdapter."
			+ EISAdapter.class.getName());

	private EnvironmentInterfaceStandard ei;

	private String wspAddress;
	private String wspName;

	private String serviceType;

	public EISAdapter() {
		super(20);
	}

	/** Called before the MAS execution with the args informed in .mas2j */
	@Override
	public void init(String[] args) {
		if (args.length == 0) {
			logger
					.warning("The jar file with the EIS environment have to be informed as parameter!");
			return;
		}
		try {
			ei = EILoader.fromJarFile(new File(args[0]));

			ei.attachEnvironmentListener(new EnvironmentListener() {
				// @Override
				public void handleNewEntity(String entity) {
				}

				// @Override
				public void handleStateChange(EnvironmentState s) {
					logger.info("new state " + s);
				}

				// @Override
				public void handleDeletedEntity(String arg0,
						Collection<String> arg1) {
				}

				// @Override
				public void handleFreeEntity(String arg0,
						Collection<String> arg1) {
				}
			});
			
			// LinkedList<Parameter> initArgs = new LinkedList<Parameter>();
			Map<String, Parameter> initMapArgs = new HashMap<String, Parameter>();

			// associate agents to entities
			for (int i = 1; i < args.length; i++) {
				Term t = ASSyntax.parseTerm(args[i]);
				if (t.isStructure()) {
					Structure arg = (Structure) t;
					if (arg.getFunctor().equals("agent_entity")) {
						final String agName = arg.getTerm(0).toString();
						if (!ei.getAgents().contains(agName)) {
							ei.registerAgent(agName);
							ei.attachAgentListener(agName, this);
						}
						ei.associateEntity(agName, arg.getTerm(1).toString());
					} else if (arg.getFunctor().equals("map")) {
						initMapArgs.put(arg.getTerm(0).toString(), Translator
								.termToParameter(arg.getTerm(1)));
					}
				}
			}
			// if no association is given, use agent name = entity name
			if (args.length == 1) {
				for (String e : ei.getEntities()) {
					ei.registerAgent(e);
					ei.attachAgentListener(e, this);
					ei.associateEntity(e, e);
				}
			}

			if (ei.isInitSupported())
				ei.init(initMapArgs);

			try {
				if (ei.getState() != EnvironmentState.PAUSED)
					ei.pause(); // EIS requires a pause before running
			} catch (Exception e) {
				e.printStackTrace();
			}

			if (ei.isStartSupported()) {
				ei.start();
			}
			
			//### Metodo brutale per annientare l'output
			System.setOut(new PrintStream(new File("/dev/null")));
			
			initCartago();
			instance = this;

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Get the instance of this environment.
	 * 
	 * @return
	 */
	public static EISAdapter getInstance(){
		return instance;
	}
	
	/**
	 * Esegue una serie di operazioni che inizializzano il workspace di Cartago
	 */
	private void initCartago() {
		wspName = "default";
		String [] myargs = new String[1];
		myargs[0] = "standalone";
		
		try {
			CartagoService.startNode();
			CartagoService.installInfrastructureLayer("default");
			checkProtocols(myargs);
			logger.info("CArtAgO Environment - standalone setup succeeded.");
		} catch (CartagoException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	/**
	 * 
	 * @param args
	 * @return
	 */
	private int checkProtocols(String[] args){
		int np = 0;
			for (int i = 1; i < args.length; i++){
				String prot = args[i];
				try {
					//System.out.println("Protocol: "+prot);
					prot = prot.replace('\'', '\"');
					//System.out.println("Protocol: "+prot);
					Literal l = Literal.parseLiteral(prot);
					if (l.getFunctor().equals("protocol")){
						String protocol = l.getTerm(0).toString();
						CartagoService.installInfrastructureLayer(protocol);
						logger.info("Installed protocol "+protocol);
						np++;
					}
				} catch (Exception ex){
					ex.printStackTrace();
				}
			}
		return np;
	}

	// method of AgentListener interface
	public void handlePercept(String agent, Percept percept) {
		try {
			addPercept(agent, perceptToLiteral(percept));
		} catch (JasonException e) {
			e.printStackTrace();
		}
		informAgsEnvironmentChanged(agent); // wake up the agent
	}

	@Override
	public List<Literal> getPercepts(String agName) {
		List<Literal> percepts = super.getPercepts(agName);
		clearPercepts(agName);
		if (percepts == null)
			percepts = new ArrayList<Literal>();

		if (ei != null) {
			try {
				Map<String, Collection<Percept>> perMap = ei
						.getAllPercepts(agName);
				for (String entity : perMap.keySet()) {
					Structure strcEnt = ASSyntax.createStructure("entity",
							ASSyntax.createAtom(entity));
					for (Percept p : perMap.get(entity)) {
						percepts.add(perceptToLiteral(p).addAnnots(strcEnt));
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return percepts;
	}

	@Override
	public boolean executeAction(String agName, Structure action) {
		// checkAgRegistry(agName);
		if (ei == null) {
			logger.warning("There is no environment loaded! Ignoring action "
					+ action);
			return false;
		}
		try {
			// check case of action on an entity: ae(<action>,<entity as
			// string>)
			if (action.getArity() == 2 && action.getFunctor().equals("ae")
					&& action.getTerm(1).isString()) {
				String entity = ((StringTerm) action.getTerm(1)).getString();
				// System.out.println(agName+" doing "+action.getTerm(0)+" as "+entity);
				ei.performAction(agName, literalToAction((Literal) action
						.getTerm(0)), entity);
			} else {
				// Map<String,Percept> r =
				ei.performAction(agName, literalToAction(action));
				// logger.info("***"+r+" for "+action);
			}
			// for (ActionResult r: result) {
			// if (r.getName().equals("success"))
			return true;
			// }
		} catch (Exception e) {
			// e.printStackTrace();
			logger.warning("Error in action '" + action + "' by " + agName
					+ ": " + e);
		}
		return false;
	}
	
	/**
	 * Join an agent to the default workspace of the node
	 * 
	 * @param agName agent node
	 * @param arch agent arch. class
	 * @return the interface to act inside the workspace
	 * @throws Exception
	 */
	public ICartagoSession startSession(String agName, CAgentArch arch) throws Exception {
		if (wspAddress == null){ 
			ICartagoSession context = CartagoService.startSession(wspName,new cartago.security.AgentIdCredential(agName),arch);
			logger.info("NEW AGENT JOINED: "+agName);
			return context;
		} else {
			ICartagoSession context = CartagoService.startRemoteSession(wspName,wspAddress,serviceType, new cartago.security.AgentIdCredential(agName),arch);
			return context;
		}
	}

	/** Called before the end of MAS execution */
	@Override
	public void stop() {
		if (ei != null) {
			try {
				if (ei.isKillSupported())
					ei.kill();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		super.stop();
	}

	/** register an agent if not registered yet */
	/*
	 * protected synchronized void checkAgRegistry(final String agName) { if
	 * (!ei.getAgents().contains(agName)) { // if the agent is not registered
	 * yet, register try { System.out.println("registering "+agName);
	 * ei.registerAgent(agName); for (String entity: ei.getEntities())
	 * ei.associateEntity(agName, entity); ei.attachAgentListener(agName, new
	 * AgentListener() { public void handlePercept(String agent, Percept
	 * percept) { // wake up the agent informAgsEnvironmentChanged(agName); }
	 * }); } catch (Exception e) { e.printStackTrace(); } } }
	 */

}
